/*-------------------------------------------------------------------------
 * drawElements Quality Program Test Executor
 * ------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Test log writer.
 *//*--------------------------------------------------------------------*/

#include "xeTestLogWriter.hpp"
#include "xeXMLWriter.hpp"
#include "deStringUtil.hpp"

#include <fstream>

namespace xe
{

static const char* TEST_LOG_VERSION = "0.3.3";

/* Batch result writer. */

struct ContainerValue
{
	ContainerValue (const std::string& value_)	: value(value_) {}
	ContainerValue (const char* value_)			: value(value_) {}
	std::string value;
};

std::ostream& operator<< (std::ostream& stream, const ContainerValue& value)
{
	if (value.value.find(' ') != std::string::npos)
	{
		// Escape.
		stream << '"';
		for (std::string::const_iterator i = value.value.begin(); i != value.value.end(); i++)
		{
			if (*i == '"' || *i == '\\')
				stream << '\\';
			stream << *i;
		}
		stream << '"';
	}
	else
		stream << value.value;

	return stream;
}

static void writeSessionInfo (const SessionInfo& info, std::ostream& stream)
{
	if (!info.releaseName.empty())
		stream << "#sessionInfo releaseName " << ContainerValue(info.releaseName) << "\n";

	if (!info.releaseId.empty())
		stream << "#sessionInfo releaseId " << ContainerValue(info.releaseId) << "\n";

	if (!info.targetName.empty())
		stream << "#sessionInfo targetName " << ContainerValue(info.targetName) << "\n";

	if (!info.candyTargetName.empty())
		stream << "#sessionInfo candyTargetName " << ContainerValue(info.candyTargetName) << "\n";

	if (!info.configName.empty())
		stream << "#sessionInfo configName " << ContainerValue(info.configName) << "\n";

	if (!info.resultName.empty())
		stream << "#sessionInfo resultName " << ContainerValue(info.resultName) << "\n";

	// \note Current format uses unescaped timestamps for some strange reason.
	if (!info.timestamp.empty())
		stream << "#sessionInfo timestamp " << info.timestamp << "\n";
}

static void writeTestCase (const TestCaseResultData& caseData, std::ostream& stream)
{
	stream << "\n#beginTestCaseResult " << caseData.getTestCasePath() << "\n";

	if (caseData.getDataSize() > 0)
	{
		stream.write((const char*)caseData.getData(), caseData.getDataSize());

		deUint8 lastCh = caseData.getData()[caseData.getDataSize()-1];
		if (lastCh != '\n' && lastCh != '\r')
			stream << "\n";
	}

	TestStatusCode dataCode = caseData.getStatusCode();
	if (dataCode == TESTSTATUSCODE_CRASH	||
		dataCode == TESTSTATUSCODE_TIMEOUT	||
		dataCode == TESTSTATUSCODE_TERMINATED)
		stream << "#terminateTestCaseResult " << getTestStatusCodeName(dataCode) << "\n";
	else
		stream << "#endTestCaseResult\n";
}

void writeTestLog (const BatchResult& result, std::ostream& stream)
{
	writeSessionInfo(result.getSessionInfo(), stream);

	stream << "#beginSession\n";

	for (int ndx = 0; ndx < result.getNumTestCaseResults(); ndx++)
	{
		ConstTestCaseResultPtr caseData = result.getTestCaseResult(ndx);
		writeTestCase(*caseData, stream);
	}

	stream << "\n#endSession\n";
}

void writeBatchResultToFile (const BatchResult& result, const char* filename)
{
	std::ofstream str(filename, std::ofstream::binary|std::ofstream::trunc);
	writeTestLog(result, str);
	str.close();
}

/* Test result log writer. */

static const char* getImageFormatName (ri::Image::Format format)
{
	switch (format)
	{
		case ri::Image::FORMAT_RGB888:		return "RGB888";
		case ri::Image::FORMAT_RGBA8888:	return "RGBA8888";
		default:
			DE_ASSERT(false);
			return DE_NULL;
	}
}

static const char* getImageCompressionName (ri::Image::Compression compression)
{
	switch (compression)
	{
		case ri::Image::COMPRESSION_NONE:	return "None";
		case ri::Image::COMPRESSION_PNG:	return "PNG";
		default:
			DE_ASSERT(false);
			return DE_NULL;
	}
}

static const char* getSampleValueTagName (ri::ValueInfo::ValueTag tag)
{
	switch (tag)
	{
		case ri::ValueInfo::VALUETAG_PREDICTOR:	return "Predictor";
		case ri::ValueInfo::VALUETAG_RESPONSE:	return "Response";
		default:
			DE_ASSERT(false);
			return DE_NULL;
	}
}

inline const char* getBoolName (bool val)
{
	return val ? "True" : "False";
}

// \todo [2012-09-07 pyry] Move to tcutil?
class Base64Formatter
{
public:
	const deUint8*	data;
	int				numBytes;

	Base64Formatter (const deUint8* data_, int numBytes_) : data(data_), numBytes(numBytes_) {}
};

std::ostream& operator<< (std::ostream& str, const Base64Formatter& fmt)
{
	static const char s_base64Table[64] =
	{
		'A','B','C','D','E','F','G','H','I','J','K','L','M',
		'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
		'a','b','c','d','e','f','g','h','i','j','k','l','m',
		'n','o','p','q','r','s','t','u','v','w','x','y','z',
		'0','1','2','3','4','5','6','7','8','9','+','/'
	};

	const deUint8*	data		= fmt.data;
	int				numBytes	= fmt.numBytes;
	int				srcNdx		= 0;

	DE_ASSERT(data && (numBytes > 0));

	/* Loop all input chars. */
	while (srcNdx < numBytes)
	{
		int		numRead	= de::min(3, numBytes - srcNdx);
		deUint8	s0		= data[srcNdx];
		deUint8	s1		= (numRead >= 2) ? data[srcNdx+1] : 0;
		deUint8	s2		= (numRead >= 3) ? data[srcNdx+2] : 0;
		char	d[4];

		srcNdx += numRead;

		d[0] = s_base64Table[s0 >> 2];
		d[1] = s_base64Table[((s0&0x3)<<4) | (s1>>4)];
		d[2] = s_base64Table[((s1&0xF)<<2) | (s2>>6)];
		d[3] = s_base64Table[s2&0x3F];

		if (numRead < 3) d[3] = '=';
		if (numRead < 2) d[2] = '=';

		/* Write data. */
		str.write(&d[0], sizeof(d));
	}

	return str;
}

inline Base64Formatter toBase64 (const deUint8* bytes, int numBytes) { return Base64Formatter(bytes, numBytes); }

static const char* getStatusName (bool value)
{
	return value ? "OK" : "Fail";
}

static void writeResultItem (const ri::Item& item, xml::Writer& dst)
{
	using xml::Writer;

	switch (item.getType())
	{
		case ri::TYPE_RESULT:
			// Ignored here, written at end.
			break;

		case ri::TYPE_TEXT:
			dst << Writer::BeginElement("Text") << static_cast<const ri::Text&>(item).text << Writer::EndElement;
			break;

		case ri::TYPE_NUMBER:
		{
			const ri::Number& number = static_cast<const ri::Number&>(item);
			dst << Writer::BeginElement("Number")
				<< Writer::Attribute("Name",		number.name)
				<< Writer::Attribute("Description",	number.description)
				<< Writer::Attribute("Unit",		number.unit)
				<< Writer::Attribute("Tag",			number.tag)
				<< number.value
				<< Writer::EndElement;
			break;
		}

		case ri::TYPE_IMAGE:
		{
			const ri::Image& image = static_cast<const ri::Image&>(item);
			dst << Writer::BeginElement("Image")
				<< Writer::Attribute("Name",			image.name)
				<< Writer::Attribute("Description",		image.description)
				<< Writer::Attribute("Width",			de::toString(image.width))
				<< Writer::Attribute("Height",			de::toString(image.height))
				<< Writer::Attribute("Format",			getImageFormatName(image.format))
				<< Writer::Attribute("CompressionMode",	getImageCompressionName(image.compression))
				<< toBase64(&image.data[0], (int)image.data.size())
				<< Writer::EndElement;
			break;
		}

		case ri::TYPE_IMAGESET:
		{
			const ri::ImageSet& imageSet = static_cast<const ri::ImageSet&>(item);
			dst << Writer::BeginElement("ImageSet")
				<< Writer::Attribute("Name",		imageSet.name)
				<< Writer::Attribute("Description",	imageSet.description);

			for (int ndx = 0; ndx < imageSet.images.getNumItems(); ndx++)
				writeResultItem(imageSet.images.getItem(ndx), dst);

			dst << Writer::EndElement;
			break;
		}

		case ri::TYPE_SHADER:
		{
			const ri::Shader&	shader		= static_cast<const ri::Shader&>(item);
			const char*			tagName		= DE_NULL;

			switch (shader.shaderType)
			{
				case ri::Shader::SHADERTYPE_VERTEX:				tagName = "VertexShader";			break;
				case ri::Shader::SHADERTYPE_FRAGMENT:			tagName = "FragmentShader";			break;
				case ri::Shader::SHADERTYPE_GEOMETRY:			tagName = "GeometryShader";			break;
				case ri::Shader::SHADERTYPE_TESS_CONTROL:		tagName = "TessControlShader";		break;
				case ri::Shader::SHADERTYPE_TESS_EVALUATION:	tagName = "TessEvaluationShader";	break;
				case ri::Shader::SHADERTYPE_COMPUTE:			tagName = "ComputeShader";			break;
				default:
					throw Error("Unknown shader type");
			}

			dst << Writer::BeginElement(tagName)
				<< Writer::Attribute("CompileStatus",	getStatusName(shader.compileStatus));

			writeResultItem(shader.source, dst);
			writeResultItem(shader.infoLog, dst);

			dst << Writer::EndElement;
			break;
		}

		case ri::TYPE_SHADERPROGRAM:
		{
			const ri::ShaderProgram& program = static_cast<const ri::ShaderProgram&>(item);
			dst << Writer::BeginElement("ShaderProgram")
				<< Writer::Attribute("LinkStatus",	getStatusName(program.linkStatus));

			writeResultItem(program.linkInfoLog, dst);

			for (int ndx = 0; ndx < program.shaders.getNumItems(); ndx++)
				writeResultItem(program.shaders.getItem(ndx), dst);

			dst << Writer::EndElement;
			break;
		}

		case ri::TYPE_SHADERSOURCE:
			dst << Writer::BeginElement("ShaderSource") << static_cast<const ri::ShaderSource&>(item).source << Writer::EndElement;
			break;

		case ri::TYPE_SPIRVSOURCE:
			dst << Writer::BeginElement("SpirVAssemblySource") << static_cast<const ri::SpirVSource&>(item).source << Writer::EndElement;
			break;

		case ri::TYPE_INFOLOG:
			dst << Writer::BeginElement("InfoLog") << static_cast<const ri::InfoLog&>(item).log << Writer::EndElement;
			break;

		case ri::TYPE_SECTION:
		{
			const ri::Section& section = static_cast<const ri::Section&>(item);
			dst << Writer::BeginElement("Section")
				<< Writer::Attribute("Name",		section.name)
				<< Writer::Attribute("Description",	section.description);

			for (int ndx = 0; ndx < section.items.getNumItems(); ndx++)
				writeResultItem(section.items.getItem(ndx), dst);

			dst << Writer::EndElement;
			break;
		}

		case ri::TYPE_KERNELSOURCE:
			dst << Writer::BeginElement("KernelSource") << static_cast<const ri::KernelSource&>(item).source << Writer::EndElement;
			break;

		case ri::TYPE_COMPILEINFO:
		{
			const ri::CompileInfo& compileInfo = static_cast<const ri::CompileInfo&>(item);
			dst << Writer::BeginElement("CompileInfo")
				<< Writer::Attribute("Name",			compileInfo.name)
				<< Writer::Attribute("Description",		compileInfo.description)
				<< Writer::Attribute("CompileStatus",	getStatusName(compileInfo.compileStatus));

			writeResultItem(compileInfo.infoLog, dst);

			dst << Writer::EndElement;
			break;
		}

		case ri::TYPE_EGLCONFIG:
		{
			const ri::EglConfig& config = static_cast<const ri::EglConfig&>(item);
			dst << Writer::BeginElement("EglConfig")
				<< Writer::Attribute("BufferSize",				de::toString(config.bufferSize))
				<< Writer::Attribute("RedSize",					de::toString(config.redSize))
				<< Writer::Attribute("GreenSize",				de::toString(config.greenSize))
				<< Writer::Attribute("BlueSize",				de::toString(config.blueSize))
				<< Writer::Attribute("LuminanceSize",			de::toString(config.luminanceSize))
				<< Writer::Attribute("AlphaSize",				de::toString(config.alphaSize))
				<< Writer::Attribute("AlphaMaskSize",			de::toString(config.alphaMaskSize))
				<< Writer::Attribute("BindToTextureRGB",		getBoolName(config.bindToTextureRGB))
				<< Writer::Attribute("BindToTextureRGBA",		getBoolName(config.bindToTextureRGBA))
				<< Writer::Attribute("ColorBufferType",			config.colorBufferType)
				<< Writer::Attribute("ConfigCaveat",			config.configCaveat)
				<< Writer::Attribute("ConfigID",				de::toString(config.configID))
				<< Writer::Attribute("Conformant",				config.conformant)
				<< Writer::Attribute("DepthSize",				de::toString(config.depthSize))
				<< Writer::Attribute("Level",					de::toString(config.level))
				<< Writer::Attribute("MaxPBufferWidth",			de::toString(config.maxPBufferWidth))
				<< Writer::Attribute("MaxPBufferHeight",		de::toString(config.maxPBufferHeight))
				<< Writer::Attribute("MaxPBufferPixels",		de::toString(config.maxPBufferPixels))
				<< Writer::Attribute("MaxSwapInterval",			de::toString(config.maxSwapInterval))
				<< Writer::Attribute("MinSwapInterval",			de::toString(config.minSwapInterval))
				<< Writer::Attribute("NativeRenderable",		getBoolName(config.nativeRenderable))
				<< Writer::Attribute("RenderableType",			config.renderableType)
				<< Writer::Attribute("SampleBuffers",			de::toString(config.sampleBuffers))
				<< Writer::Attribute("Samples",					de::toString(config.samples))
				<< Writer::Attribute("StencilSize",				de::toString(config.stencilSize))
				<< Writer::Attribute("SurfaceTypes",			config.surfaceTypes)
				<< Writer::Attribute("TransparentType",			config.transparentType)
				<< Writer::Attribute("TransparentRedValue",		de::toString(config.transparentRedValue))
				<< Writer::Attribute("TransparentGreenValue",	de::toString(config.transparentGreenValue))
				<< Writer::Attribute("TransparentBlueValue",	de::toString(config.transparentBlueValue))
				<< Writer::EndElement;
			break;
		}

		case ri::TYPE_EGLCONFIGSET:
		{
			const ri::EglConfigSet& configSet = static_cast<const ri::EglConfigSet&>(item);
			dst << Writer::BeginElement("EglConfigSet")
				<< Writer::Attribute("Name",			configSet.name)
				<< Writer::Attribute("Description",		configSet.description);

			for (int ndx = 0; ndx < configSet.configs.getNumItems(); ndx++)
				writeResultItem(configSet.configs.getItem(ndx), dst);

			dst << Writer::EndElement;
			break;
		}

		case ri::TYPE_SAMPLELIST:
		{
			const ri::SampleList& list = static_cast<const ri::SampleList&>(item);
			dst << Writer::BeginElement("SampleList")
				<< Writer::Attribute("Name",		list.name)
				<< Writer::Attribute("Description",	list.description);

			writeResultItem(list.sampleInfo, dst);

			for (int ndx = 0; ndx < list.samples.getNumItems(); ndx++)
				writeResultItem(list.samples.getItem(ndx), dst);

			dst << Writer::EndElement;
			break;
		}

		case ri::TYPE_SAMPLEINFO:
		{
			const ri::SampleInfo& info = static_cast<const ri::SampleInfo&>(item);
			dst << Writer::BeginElement("SampleInfo");
			for (int ndx = 0; ndx < info.valueInfos.getNumItems(); ndx++)
				writeResultItem(info.valueInfos.getItem(ndx), dst);
			dst << Writer::EndElement;
			break;
		}

		case ri::TYPE_VALUEINFO:
		{
			const ri::ValueInfo& info = static_cast<const ri::ValueInfo&>(item);
			dst << Writer::BeginElement("ValueInfo")
				<< Writer::Attribute("Name",		info.name)
				<< Writer::Attribute("Description",	info.description)
				<< Writer::Attribute("Tag",			getSampleValueTagName(info.tag));
			if (!info.unit.empty())
				dst << Writer::Attribute("Unit", info.unit);
			dst << Writer::EndElement;
			break;
		}

		case ri::TYPE_SAMPLE:
		{
			const ri::Sample& sample = static_cast<const ri::Sample&>(item);
			dst << Writer::BeginElement("Sample");
			for (int ndx = 0; ndx < sample.values.getNumItems(); ndx++)
				writeResultItem(sample.values.getItem(ndx), dst);
			dst << Writer::EndElement;
			break;
		}

		case ri::TYPE_SAMPLEVALUE:
		{
			const ri::SampleValue& value = static_cast<const ri::SampleValue&>(item);
			dst << Writer::BeginElement("Value")
				<< value.value
				<< Writer::EndElement;
			break;
		}

		default:
			XE_FAIL("Unsupported result item");
	}
}

void writeTestResult (const TestCaseResult& result, xe::xml::Writer& xmlWriter)
{
	using xml::Writer;

	xmlWriter << Writer::BeginElement("TestCaseResult")
			  << Writer::Attribute("Version", TEST_LOG_VERSION)
			  << Writer::Attribute("CasePath", result.casePath)
			  << Writer::Attribute("CaseType", getTestCaseTypeName(result.caseType));

	for (int ndx = 0; ndx < result.resultItems.getNumItems(); ndx++)
		writeResultItem(result.resultItems.getItem(ndx), xmlWriter);

	// Result item is not logged until end.
	xmlWriter << Writer::BeginElement("Result")
			  << Writer::Attribute("StatusCode", getTestStatusCodeName(result.statusCode))
			  << result.statusDetails
			  << Writer::EndElement;

	xmlWriter << Writer::EndElement;
}

void writeTestResult (const TestCaseResult& result, std::ostream& stream)
{
	xml::Writer xmlWriter(stream);
	stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
	writeTestResult(result, xmlWriter);
}

void writeTestResultToFile (const TestCaseResult& result, const char* filename)
{
	std::ofstream str(filename, std::ofstream::binary|std::ofstream::trunc);
	writeTestResult(result, str);
	str.close();
}

} // xe
